-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fixing #7140: Implementing new lazy vals scheme #14545
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
/cc @WojciechMazur You may want to have a look at this, from the point of view of Scala Native. |
@sjrd Thank you for the mention. Overall I think that these changes would not be problematic in Scala Native, but still would need to adapt Lazy Vals in Scala Native compiler plugin, at least for now. The only thing that would change is the field we would calculate pointer off. |
I don't think so, as far as I can tell this can't be fixed without requiring Java 9+ cf #9013 |
* if current.isInstanceOf[A] then | ||
* current.asInstanceOf[A] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This must come last. If A
erases to Object
, this test will be true
for any non-null
value, including instances of NULL
, Waiting
and Evaluating
!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, it should mention the return
explicitly in this variant:
* if current.isInstanceOf[A] then | |
* current.asInstanceOf[A] | |
* if current.isInstanceOf[A] then | |
* return current.asInstanceOf[A] |
Same for return null
below.
* case current: A => | ||
* current |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, this must come last.
@smarter right, if we extend the standard library, we cannot use these extensions when producing bytecode which is supposed to work with an older compiler. Also all the declarations of vals, classes etc. added to Regarding the tests the easiest way to find some examples of how this is done is just to search for files with names ending with |
@olsdavis I could help you with this part if you don't know how to do it. |
@nicolasstucki Yes, sure! Thank you |
@prolativ are we still missing a way to execute the run tests using the 3.0 library? Without this, we cannot easily test that this feature actually used the release flag and generated compatible code. |
@olsdavis you can add the following tests files. These show that the current library is compatible with code generated with older versions of the compiler.
// Compiled with 3.0.0 and run with current compiler
class Foo:
lazy val x =
println("computing x")
"x"
lazy val y =
println("computing y")
"y"
@main def Test =
val foo = new Foo
println(foo.x)
println(foo.y)
// Compiled with 3.1.0 and run with current compiler
class Foo:
lazy val x =
println("computing x")
"x"
lazy val y =
println("computing y")
"y"
@main def Test =
val foo = new Foo
println(foo.x)
println(foo.y)
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think those are all the changes needed to support the release flag. I still have to figure out how to test the runtime when targeting an older release.
clz.getDeclaredFields.nn.foreach(println(_)) | ||
val field = clz.getDeclaredField(name) | ||
if java.lang.reflect.Modifier.isStatic(field.nn.getModifiers()) then | ||
unsafe.staticFieldOffset(field) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this is compatible with code compiled with 3.0
. Could would add a case where this would be needed in the new implementation to tests/run/lazyVals_c3.0.0.scala
and tests/run/lazyVals_c3.1.0.scala
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we have conflicts, we should create a variant of getOffset
for the new encoding and keep the old one for backward compatibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR also adds getStaticOffset and the compiler should know which fields are static, so couldn't it emit calls to getStaticOffset when needed instead of checking this at runtime?
val body = initBlock(ValDef(current, ref(target)) :: If(ref(current).equal(nullLiteral), unevaluated, ifNotNull) :: Nil) | ||
val mainLoop = WhileDo(EmptyTree, body) // becomes: while (true) do { body } | ||
val ret = DefDef(methodSymbol, initBlock(discardDef :: mainLoop :: Nil)) | ||
ret | ||
} | ||
|
||
def transformMemberDefThreadSafe(x: ValOrDefDef)(using Context): Thicket = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When targetting an older version such as 3.0 we need to generate the old version of the code.
I suggest making two variants of transformMemberDefThreadSafe
one with the old code and one with the new code. Do the same for mkThreadSafeDef
. Then transformMemberDefThreadSafe
can dispatch to the correct one as follows
import dotty.tools.dotc.config.ScalaRelease.*
def transformMemberDefThreadSafe(x: ValOrDefDef)(using Context): Thicket =
// TODO find more meaningful names, not old/new.
if ctx.scalaRelease <= Release3_1 then transformMemberDefThreadSafeOld(x)
else transformMemberDefThreadSafeNew(x)
Note: use Release3_0
as a starting point. Otherwise, you will always get the old version as the main is also on 3.1. This is something that we have to change when going to 3.2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use a variant of lazyVals_c3.0.0.scala
named lazyVals_r3.0.scala
to have a test that sets the scala release flag to 3.0. This will compile to code with the current compiler but target the old stdlib. Unfortunately, the test will run with the current library. We still need to figure out the simplest way to run with the old library.
The |
Needs rebase on the current main branch |
* Used to indicate the state of a lazy val that is being | ||
* evaluated and of which other threads await the result. | ||
*/ | ||
final class Waiting: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All new definitions in this class should be added to
project/MiMaFilters.scala
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals.getStaticOffset"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals.getStaticOffset"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals.objCAS"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals.objCAS"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.evaluating"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.getStaticOffset"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.nullValued"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.objCas"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.waiting"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.waitingAwaitRelease"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.waitingRelease"),
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.LazyVals$Evaluating$"),
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.LazyVals$NULL$"),
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.LazyVals$Waiting"),
ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.LazyVals.Evaluating"),
ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.LazyVals.NULL"),
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be tested with
sbt:scala3> reload;scala3-interfaces/mimaReportBinaryIssues;scala3-library-bootstrapped/mimaReportBinaryIssues
* Make -scala-release an experimental setting * Add better checks of of TASTy version * Validate names of releases inside since annotations * Extend Vulpix to check actual counts and possitions of errors from other compilers
The previous check did not consider the case where the self constructor was itself a block that originated from named and default parameters in the actual self constructor call. That gave a false negative for some code in akka. The negative was not discovered before since we did not propagate the correct context information into the expression of a block.
When parsing a `TRY` if there is an empty catch block instead of just returning a syntax error return an incomplete if you're at the EOF. This ensures that in the REPL if you are in a position like: ```scala scala> try { | ??? | } catch ``` And you hit enter you'll still be able to continue. Fixes scala#4393
- Implements a propotype of the new lazy vals scheme Fix/Fixing/Fixes/Close/Closing/Refs scala#7140
Closing and continuing work in #15207, comments will be addressed there. |
Implementing the new lazy vals scheme mentioned in #6979, fixing #7140; note that, however:
DottyBytecodeTests.lazyFields
does not pass anymore, as the new number of instructions generated by the lazy vals scheme is higher (126
);